﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RayDen.Library.Components;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Scene;

using TriangleMesh = RayDen.Library.Components.TriangleMesh;


namespace RayDen.SmallPT.Light
{
    public class TriangleLight : ILight
    {
        public string Name
        {
            get { throw new NotImplementedException(); }
        }

        public bool IsDelta
        {
            get { return false; }
        }


        internal float area;
        public Normal TriangleNormal;
        public Triangle tri;
        public int triangleIndex;
        [NonSerialized]
        public SceneGeometryInfo scene;

        public TriangleMesh Owner
        {
            get { return tri.Owner; }
        }
        public RgbSpectrum Gain;

        public TriangleLight(SceneGeometryInfo sc)
        {
            this.scene = sc;
        }
        public TriangleLight(SceneGeometryInfo sc, ref Triangle tri, int triIndex = -1, RgbSpectrum gain = new RgbSpectrum())
            : this(sc)
        {
            Gain = gain;
            if (triIndex != -1)
                triangleIndex = triIndex;

            this.tri = tri;
            var computeNormal = true;
            //Configuration.Instance.Get("Integrator.ComputeNormals", false);
            var normalInverse = true;
            //Configuration.Instance.Get("Integrator.InverseLightNormals", false);

            var multiplicator = normalInverse ? -1f : 1f;
            this.TriangleNormal = multiplicator * (computeNormal ? tri.ComputeNormal(ref scene.Vertices) : tri.Owner.GetTriangleNormal(triIndex, 0));
            this.area = tri.AreaV(ref scene.Vertices);
        }

        #region IMaterial Members

        public bool IsLightSource() { return true; }
        public bool IsDiffuse() { return false; }
        public bool IsSpecular() { return false; }

        #endregion


        public RgbSpectrum Le(Vector wo)
        {
            var sampleN = TriangleNormal;

            if (Normal.Dot(ref sampleN, ref wo) <= 0f)
                return new RgbSpectrum(0f);

            return Gain; // Light sources are supposed to have flat color
        }

        public int SamplePoint(out Point samplePoint, float u0, float u1, out float pdf)
        {
            float b0, b1, b2;
            tri.Sample(ref scene.Vertices, u0, u1, out samplePoint, out b0, out b1, out b2);

            pdf = 1f / this.area;
            return triangleIndex;
        }

        public RgbSpectrum Sample(ref Point p, ref Normal N, float u2, float u0, float u1, out RayInfo shadowRay, out float pdf)
        {
            Point samplePoint = new Point();
            float b0, b1, b2;

            tri.Sample(ref scene.Vertices, u0, u1, out samplePoint, out b0, out b1, out b2);
            //var sampleN = TriangleNormal;
            //var N = n;

            Vector wi = samplePoint - p;
            //wi.Normalize();
            float distanceSquared = wi.Length2();
            float distance = MathLab.Sqrt(distanceSquared);
            wi /= distance;

            var nw = -wi;
            float sampleNdotMinusWi = Normal.Dot(ref TriangleNormal, ref nw);
            float NdotWi = Normal.Dot(ref N, ref wi);
            if ((sampleNdotMinusWi <= 0f) || (NdotWi <= 0f))
            {
                pdf = 0f;
                shadowRay = null;
                return new RgbSpectrum(0f);
            }

            shadowRay = new RayInfo(p, wi, MathLab.RAY_EPSILON, distance - MathLab.RAY_EPSILON);
            pdf = (distanceSquared / (sampleNdotMinusWi * area));

            // Using 0.1 instead of 0.0 to cut down fireflies
            if (pdf <= 0.1f)
            {
                pdf = 0f;
                return new RgbSpectrum(0);
            }
            return Gain;
        }

        public RgbSpectrum SamplePhoton(SceneGeometryInfo scene, float u0, float u1, float u2, float u3, float u4, out float pdf, out RayInfo ray)
        {
            float b0, b1, b2;
            Point orig;
            tri.Sample(ref scene.Vertices, u0, u1, out orig, out b0, out b1, out b2);

            // Ray direction
            var sampleN = this.TriangleNormal;
            Vector dir = MC.UniformSampleSphere(u2, u3);
            float RdotN = Normal.Dot(ref dir, ref sampleN);
            if (RdotN < 0f)
            {
                dir *= -1f;
                RdotN = -RdotN;
            }

            ray = new RayInfo(orig, dir);

            pdf = MathLab.INVTWOPI / area;

            return Gain * RdotN;
        }



        //public RgbSpectrum Sample_Lightsource( CLSceneModel scen, float u0, float u1, float u2, float u3 , out Vector point, out float pdf, out RayInfo ray ) {
        //    float b0, b1, b2;
        //    Vector orig;
        //    tri.Sample(ref scen.vertices, u0, u1, out orig, out b0, out b1, out b2);

        //    // Ray direction
        //    var sampleN = this.TriangleNormal;
        //    Vector dir = MCProcessor.UniformSampleSphere(u2, u3);
        //    float RdotN = Normal.Dot(ref dir, ref sampleN);
        //    if( RdotN < 0f ) {
        //        dir *= -1f;
        //        RdotN = -RdotN;
        //    }

        //    ray = new RayInfo(orig, dir);

        //    pdf = MathLab.INVTWOPI / area;
        //    point = orig;

        //    return lightMaterial.GetGain();// * RdotN
        //}
        //public override LightSourceType LightType
        //{
        //    get { return LightSourceType.TYPE_TRIANGLE; }
        //}
    }
}
